/**
 * 
 */
package br.embedded.ufcg.rmi;

import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jgrapht.Graph;
import org.jgrapht.generate.ScaleFreeGraphGenerator;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleWeightedGraph;

import br.edu.ufcg.embedded.mysocial.UserGenerator;
import br.edu.ufcg.embedded.mysocial.VertexGenerator;
import br.edu.ufcg.embedded.util.FileConsumer;
import br.edu.ufcg.embedded.util.FileManager;
import br.edu.ufcg.embedded.util.Topology;
import br.embedded.socialapp.data.FriendshipDAO;


/**
 * @author daniel
 *
 */
public class Server extends UnicastRemoteObject implements Process
{
	private static int MAX_CONNECTIONS;
	private static int NUMBER_OF_VERTICES;
	private static int VERTEX_DEGREE;
	private static double UPDATE_DEGREE;
	private static Topology TOPOLOGY;
	private static String RMI_REGISTRY;
	private static final String STATISTICS_PATH = "statistics.txt";
	private static String BULK_PATH;
	private static final String VERSION = "1.0.0.0";
	
	private Graph<String, DefaultEdge> graph;
	private Map<String, StatisticsInfo> connectionMap;
	private Map<String, List<String>> jobMap;
	
	private List<List<String>> lists;
	
	private Monitor monitor;
	private FileConsumer fileConsumer;
	
	public Server() throws RemoteException
	{
		super();
		
		System.out.println("Server version: " + VERSION);
		
		connectionMap = new HashMap<String, StatisticsInfo>(MAX_CONNECTIONS);
		jobMap = new HashMap<String, List<String>>(MAX_CONNECTIONS);
		createGraph();
		createLists();
		
		System.out.println("Server: the graph is ready to be consumed.");
		
		monitor = new Monitor(this);
		monitor.start();
		
		fileConsumer = new FileConsumer(BULK_PATH, ".ttt2");
		fileConsumer.start();
		
		System.gc();
	}
	
/*	public synchronized void reset()
	{
		unbind("a");
		
		connectionMap = new HashMap<String, StatisticsInfo>(MAX_CONNECTIONS);
		jobMap = new HashMap<String, List<String>>(MAX_CONNECTIONS);
		updateGraph();
		createLists();
		
		FriendshipDAO.getInstance().truncate();	
		
		rebind("b", this);
	}*/
	
	@Override
	public synchronized Graph<String, DefaultEdge> getGraph() throws RemoteException {
		
		return graph;
	}
	
	@Override
	public synchronized List<String> getJobList(String ip) throws RemoteException {
		
		List<String> jobList = new ArrayList<String>(graph.vertexSet());//jobMap.get(ip);
		jobMap.put(ip, null);
		System.gc();
		return jobList;
	}
	
	@Override
	public synchronized void register(String client) throws RemoteException {
		
		if (!connectionMap.containsKey(client) && connectionMap.size() < MAX_CONNECTIONS)
		{
			connectionMap.put(client, null);
			jobMap.put(client, getAvailableList());
			
			System.out.println("Server: a new client: " + client + " was registered ");
		}
		else
			System.out.println("Server: the client: " + client + " is already registered.");
	}
	
	@Override
	public synchronized void setStatisticsInfo(StatisticsInfo statisticsInfo) throws RemoteException {
		
		if (connectionMap.containsKey(statisticsInfo.getNodeIp())) {
			statisticsInfo.setTopology(TOPOLOGY.toString());	
			connectionMap.put(statisticsInfo.getNodeIp(), statisticsInfo);
			System.out.println("Server: the client " + statisticsInfo.getNodeIp() + " has finished. Elapsed time: " + statisticsInfo.getElapsedTime());
		}
	}
	
	public synchronized boolean checkConnections()
	{
		int count = 0;
		
		for (StatisticsInfo statisticsInfo : connectionMap.values()) {
			if (statisticsInfo == null)
				break;
			else
				count++;
		}
		
		if (count == MAX_CONNECTIONS)
			return true;
		
		return false;
	}
	
	public synchronized void shutdown()
	{
		fileConsumer.producerFinished();
		
		try {
			fileConsumer.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		writeStatistics();
		
		unbind();
		
		System.out.println("Server is shutting down.");
		
		System.exit(0);
	}
	
	/*private void updateGraph2()
	{
		List<String> list = new ArrayList<String>(graph.vertexSet());
		int edges = graph.edgeSet().size();
		
		List<Integer> vertexDegrees = new ArrayList<Integer>();
		//int[] vertexDegrees = new int[2 * edges];
		
		int index = 0;
		
		for (int i = 0; i < list.size(); i++) {
			int degree = Graphs.neighborListOf(graph, list.get(i)).size();
			
			for (int j = 0; j < degree; j++)
				vertexDegrees.add(i);
				//vertexDegrees[index++] = i;
		}
		
		Random random = new Random();
		
		int newEdges = ( (int) (edges * UPDATE_DEGREE));
		int k = 0;
		
		while (k < newEdges)
		{
			int sourceIndex = random.nextInt(vertexDegrees.size());
			int targetIndex = random.nextInt(vertexDegrees.size());
			int sourceIndex = random.nextInt(vertexDegrees.length);
			int targetIndex = random.nextInt(vertexDegrees.length);
			
			String sourceVertex = list.get(vertexDegrees.get(sourceIndex));
			String targetVertex = list.get(vertexDegrees.get(targetIndex));
			String sourceVertex = list.get(vertexDegrees[sourceIndex]);
			String targetVertex = list.get(vertexDegrees[targetIndex]);
			
			if (!sourceVertex.equalsIgnoreCase(targetVertex) && !(graph.containsEdge(sourceVertex, targetVertex) && graph.containsEdge(targetVertex, sourceVertex)))
			{
				graph.addEdge(sourceVertex, targetVertex);
				k++;
			}
		}
		
		System.out.println("Server: Graph was updated. Now it has " 
				+ graph.vertexSet().size() + " nodes and " + graph.edgeSet().size() + " edges.");
	}
	
	private void updateGraph() {
		List<String> list = new ArrayList<String>(graph.vertexSet());
		int edges = graph.edgeSet().size();
		List<Integer> vertexDegrees = new ArrayList<Integer>();

		for (int i = 0; i < list.size(); i++) {
			int degree = Graphs.neighborListOf(graph, list.get(i)).size();

			for (int j = 0; j < degree; j++)
				vertexDegrees.add(i);
		}

		Random random = new Random();
		int newEdges = ((int) (edges * UPDATE_DEGREE));
		int k = 0;

		while (k < newEdges) {
			int sourceIndex = random.nextInt(vertexDegrees.size());
			int targetIndex = random.nextInt(vertexDegrees.size());

			String sourceVertex = list.get(vertexDegrees.get(sourceIndex));
			String targetVertex = list.get(vertexDegrees.get(targetIndex));

			if (!sourceVertex.equalsIgnoreCase(targetVertex)
					&& !(graph.containsEdge(sourceVertex, targetVertex) && graph
							.containsEdge(targetVertex, sourceVertex))) {
				graph.addEdge(sourceVertex, targetVertex);
				k++;
			}
		}
	}*/
	
	public synchronized void writeStatistics()
	{
		System.out.println("Server is writting the statistics...");
		
		try {
			FileManager fileManager = new FileManager();
			fileManager.openOrCreateFile(STATISTICS_PATH);
			
			for (StatisticsInfo statisticsInfo : connectionMap.values()) {
				fileManager.write(statisticsInfo.toString());
			}
			fileManager.close();
			fileManager = null;
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
	
	private void createGraph()
	{
		if (TOPOLOGY == Topology.ScaleFree)
		{
			graph = new SimpleWeightedGraph<String, DefaultEdge>(DefaultEdge.class);
			ScaleFreeGraphGenerator<String, DefaultEdge> generator = new ScaleFreeGraphGenerator<String, DefaultEdge>(NUMBER_OF_VERTICES);
			generator.generateGraph(graph, new VertexGenerator(), null);
			generator = null;
		}
		else {
			
			List<String> usersList = UserGenerator.generateUsers(NUMBER_OF_VERTICES);
			graph = UserGenerator.buildFriendshipGraph(usersList, VERTEX_DEGREE);
			usersList = null;
		}
		
		System.gc();
		
		System.out.println("Server: Graph was generated with " 
				+ graph.vertexSet().size() + " nodes and " + graph.edgeSet().size() + " edges.");
	}
		
	private void createLists()
	{
		this.lists = new ArrayList<List<String>>(MAX_CONNECTIONS);
		
		if (MAX_CONNECTIONS == 1)
		{
			this.lists.add(new ArrayList<String>(graph.vertexSet()));
			return;
		}
		
		List<List<String>> tmpLists = new ArrayList<List<String>>(MAX_CONNECTIONS);
		
		for (int i = 0; i < MAX_CONNECTIONS; i++) {
			tmpLists.add(new ArrayList<String>());
		}
		
		int j = 0;
		
		for (String u : graph.vertexSet()) {
			tmpLists.get(j % MAX_CONNECTIONS).add(u);
			j++;
			u = null;
		}
		
		for (int i = 0; i < tmpLists.size(); i++) {
			this.lists.add(tmpLists.get(i));
			tmpLists.set(i, null);
		}
		
		tmpLists = null;
	}
	
	private List<String> getAvailableList()
	{
		if (lists.size() > 0) {
			List<String> list = lists.remove(0);
			
			if (lists.size() == 0)
				lists = null;
			
			return list;
		}
		return null;
	}
	
	public static void main(String[] args)
	{
		if (args != null && args.length == 7)
		{
			FriendshipDAO.getInstance().truncate();
			
			try
			{
				RMI_REGISTRY = args[0];
				MAX_CONNECTIONS = Integer.parseInt(args[1]);
				NUMBER_OF_VERTICES = Integer.parseInt(args[2]);
				VERTEX_DEGREE = Integer.parseInt(args[3]);
				TOPOLOGY = Topology.valueOf(args[4]);
				BULK_PATH = args[5];
				UPDATE_DEGREE = Double.parseDouble(args[6]);
			}
			catch(Exception ex)
			{
				ex.printStackTrace();
				System.exit(1);
			}
			
			bind();
		}
		else
			System.out.println("Server cannot start. Invalid parameters input.");
	}
	
	private static void bind()
	{
		try {
			Naming.rebind(RMI_REGISTRY, new Server());
			System.out.println("The Server is running...");
		} catch (RemoteException e) {
			System.out.println(e.getMessage());
		} catch (MalformedURLException e) {
			System.out.println(e.getMessage());
		}
	}
		
	private static void unbind()
	{
		try {
			Naming.unbind(RMI_REGISTRY);
		}catch (NotBoundException e) {
			System.out.println(e.getMessage());
		}catch (MalformedURLException e) {
			System.out.println(e.getMessage());
		}catch (RemoteException e) {
			System.out.println(e.getMessage());
		} 
	}
}
